EDA — Vehicles US¶
Autor: Sandra Quinones Proyecto: Panel de análisis de anuncios de venta de vehículos
Objetivos¶
- Explorar la estructura del dataset
vehicles_us.csv. - Visualizar distribuciones clave (kilometraje, precio).
- Explorar la relación precio vs. kilometraje mediante scatter interactivo.
- Preparar visualizaciones y componentes que se reutilizan en la app Streamlit.
In [1]:
# Librerías y configuración
import pandas as pd
import numpy as np
import plotly.express as px
# Opciones (para mostrar más columnas)
pd.set_option('display.max_columns', 50)
px.defaults.template = "plotly_white"
In [2]:
# Cargar dataset (archivo en la raíz del proyecto)
car_data = pd.read_csv('vehicles_us.csv')
# Arreglo de nombres de columnas: homogeneizar (minúsculas y quitar espacios)
car_data.columns = [c.strip().lower().replace(' ', '_') for c in car_data.columns]
# Vista rápida
print("Registros:", car_data.shape[0])
display(car_data.head(20))
display(car_data.info(20))
Registros: 51525
| price | model_year | model | condition | cylinders | fuel | odometer | transmission | type | paint_color | is_4wd | date_posted | days_listed | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 9400 | 2011.0 | bmw x5 | good | 6.0 | gas | 145000.0 | automatic | SUV | NaN | 1.0 | 2018-06-23 | 19 |
| 1 | 25500 | NaN | ford f-150 | good | 6.0 | gas | 88705.0 | automatic | pickup | white | 1.0 | 2018-10-19 | 50 |
| 2 | 5500 | 2013.0 | hyundai sonata | like new | 4.0 | gas | 110000.0 | automatic | sedan | red | NaN | 2019-02-07 | 79 |
| 3 | 1500 | 2003.0 | ford f-150 | fair | 8.0 | gas | NaN | automatic | pickup | NaN | NaN | 2019-03-22 | 9 |
| 4 | 14900 | 2017.0 | chrysler 200 | excellent | 4.0 | gas | 80903.0 | automatic | sedan | black | NaN | 2019-04-02 | 28 |
| 5 | 14990 | 2014.0 | chrysler 300 | excellent | 6.0 | gas | 57954.0 | automatic | sedan | black | 1.0 | 2018-06-20 | 15 |
| 6 | 12990 | 2015.0 | toyota camry | excellent | 4.0 | gas | 79212.0 | automatic | sedan | white | NaN | 2018-12-27 | 73 |
| 7 | 15990 | 2013.0 | honda pilot | excellent | 6.0 | gas | 109473.0 | automatic | SUV | black | 1.0 | 2019-01-07 | 68 |
| 8 | 11500 | 2012.0 | kia sorento | excellent | 4.0 | gas | 104174.0 | automatic | SUV | NaN | 1.0 | 2018-07-16 | 19 |
| 9 | 9200 | 2008.0 | honda pilot | excellent | NaN | gas | 147191.0 | automatic | SUV | blue | 1.0 | 2019-02-15 | 17 |
| 10 | 19500 | 2011.0 | chevrolet silverado 1500 | excellent | 8.0 | gas | 128413.0 | automatic | pickup | black | 1.0 | 2018-09-17 | 38 |
| 11 | 8990 | 2012.0 | honda accord | excellent | 4.0 | gas | 111142.0 | automatic | sedan | grey | NaN | 2019-03-28 | 29 |
| 12 | 18990 | 2012.0 | ram 1500 | excellent | 8.0 | gas | 140742.0 | automatic | pickup | NaN | 1.0 | 2019-04-02 | 37 |
| 13 | 16500 | 2018.0 | hyundai sonata | excellent | 4.0 | gas | 22104.0 | automatic | sedan | silver | NaN | 2019-01-14 | 29 |
| 14 | 12990 | 2009.0 | gmc yukon | excellent | 8.0 | gas | 132285.0 | automatic | SUV | black | 1.0 | 2019-01-31 | 24 |
| 15 | 17990 | 2013.0 | ram 1500 | excellent | 8.0 | gas | NaN | automatic | pickup | red | 1.0 | 2018-05-15 | 111 |
| 16 | 14990 | 2010.0 | ram 1500 | excellent | 8.0 | gas | 130725.0 | automatic | pickup | red | 1.0 | 2018-12-30 | 13 |
| 17 | 13990 | 2014.0 | jeep cherokee | excellent | 6.0 | gas | 100669.0 | automatic | SUV | red | 1.0 | 2018-08-16 | 25 |
| 18 | 12500 | 2013.0 | chevrolet traverse | excellent | 6.0 | gas | 128325.0 | automatic | SUV | white | 1.0 | 2019-04-09 | 13 |
| 19 | 13990 | 2018.0 | hyundai elantra | excellent | 4.0 | gas | 31932.0 | automatic | sedan | red | NaN | 2018-08-25 | 27 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 51525 entries, 0 to 51524 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 price 51525 non-null int64 1 model_year 47906 non-null float64 2 model 51525 non-null object 3 condition 51525 non-null object 4 cylinders 46265 non-null float64 5 fuel 51525 non-null object 6 odometer 43633 non-null float64 7 transmission 51525 non-null object 8 type 51525 non-null object 9 paint_color 42258 non-null object 10 is_4wd 25572 non-null float64 11 date_posted 51525 non-null object 12 days_listed 51525 non-null int64 dtypes: float64(4), int64(2), object(7) memory usage: 5.1+ MB
None
Observaciones iniciales¶
- Normalizamos nombres de columnas a minúsculas (por consistencia).
- A continuación se limpian valores nulos en variables clave (precio, odometer, model_year) y se crea una columna auxiliar
year_bucketpara visualizaciones agrupadas por rango de año.
In [3]:
# Limpieza mínima necesaria para visualizaciones
# Convertir a numérico donde corresponde
for col in ['price', 'odometer', 'model_year']:
if col in car_data.columns:
car_data[col] = pd.to_numeric(car_data[col], errors='coerce')
# Eliminar filas sin price o odometer (no son útiles para los gráficos)
car_data = car_data.dropna(subset=['price', 'odometer'])
# Rellenar model_year nulos con la mediana (si se necesita) o mantener para filtrar
# Aquí optamos por dejar nulos y no usarlos en gráficos que requieran model_year, o completar que luego si queremos con car_data['model_year'].fillna(...)
# Para tener columnas más legibles:
car_data['model_year'] = car_data['model_year'].astype('Int64') # permite NA
# Crear columnas auxiliares: bucket de año (decadas) para color en histogramas
bins = [1900, 1990, 2000, 2010, 2015, 2018, 2020, 2022, 2025]
labels = ['<1990','1990s','2000s','2010-14','2015-17','2018-19','2020-21','2022+']
if 'model_year' in car_data.columns:
car_data['year_bucket'] = pd.cut(car_data['model_year'].astype(float), bins=bins, labels=labels, include_lowest=True)
else:
car_data['year_bucket'] = np.nan
# Muestra de confirmación
display(car_data[['price','odometer','model_year','year_bucket']].head())
| price | odometer | model_year | year_bucket | |
|---|---|---|---|---|
| 0 | 9400 | 145000.0 | 2011 | 2010-14 |
| 1 | 25500 | 88705.0 | <NA> | NaN |
| 2 | 5500 | 110000.0 | 2013 | 2010-14 |
| 4 | 14900 | 80903.0 | 2017 | 2015-17 |
| 5 | 14990 | 57954.0 | 2014 | 2010-14 |
Nota de limpieza:
- Convertimos
price,odometerymodel_yeara numérico conerrors='coerce'para que valores inválidos se marquen NA y puedan limpiarse con seguridad. - Decidimos no eliminar todas las filas con
model_yearfaltante, porque perderíamos observaciones válidas para otros análisis; en cambio, solo filtramos cuando la gráfica lo requiere.
In [4]:
# Histograma por decade (year_bucket)
if car_data['year_bucket'].notna().sum() > 0:
fig = px.histogram(
car_data,
x='odometer',
color='year_bucket',
nbins=50,
title='Distribución del kilometraje por año de fabricación (bucket)',
labels={'odometer':'Kilometraje', 'count':'Cantidad'},
category_orders={'year_bucket': ['<1990','1990s','2000s','2010-14','2015-17','2018-19','2020-21','2022+']}
)
fig.update_layout(barmode='stack', legend_title='Año (bucket)')
else:
# fallback simple si no hay year_bucket usable
fig = px.histogram(car_data, x='odometer', nbins=50, title='Distribución del kilometraje')
fig.update_layout(title_x=0.5)
fig.show()
Observaciones sobre el histograma¶
- El histograma está coloreado por intervalos de
model_year(decadas). - Puedes hacer clic en un elemento de la leyenda para aislar ese grupo y analizar su distribución.
- Esto permite ver si coches más nuevos tienden a tener menos odómetro.
In [5]:
#Gráfico de dispersión
sample_data = car_data.copy()
sample_data['model_year_filled'] = sample_data['model_year'].fillna(0)
fig = px.scatter(
sample_data,
x='odometer',
y='price',
color= 'year_bucket',
size='model_year_filled',
opacity=0.7,
hover_data=['model', 'model_year', 'fuel'],
title='Precio vs Kilometraje',
category_orders={'year_bucket': ['<1990','1990s','2000s','2010-14','2015-17','2018-19','2020-21','2022+']}
)
fig.update_layout(title_x=0.5)
fig.show()
Observaciones sobre Price vs Odometer¶
- Se usa una muestra (2000 registros) para mantener la interactividad fluida.
- El color ayuda a detectar si un tipo o rango de año tiene un patrón diferente.
- El tamaño por
model_yearayuda a ver la influencia del año (puntos más grandes = año mayor).
In [6]:
# Histograma de precios con booleana
fig = px.histogram(
car_data.query("price <= price.quantile(0.99)"), # recortamos el 1% superior para ver mejor la forma
x='price',
nbins=50,
title='Distribución de precios (recortado al 99%)',
)
fig.update_layout(xaxis_title='Precio (USD)', yaxis_title='Cantidad', title_x=0.5)
fig.show()
Resumen¶
- Limpiamos variables clave y creamos
year_bucket. - Mostramos distribuciones (kilometraje y precio) y una relación (precio vs km) con muestreo para mantener interactividad.
- Siguientes pasos: preparar métricas por modelo/plataforma, y pasar visualizaciones más limpias a Streamlit (app.py).